/*******************************************************************************
 * Copyright (c) 2011 Irineu Martins de Lima Moura (imlm2).
 * This file is part of JBeat.
 * 
 *     JBeat is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     JBeat is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 * 
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with JBeat.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Contributors:
 *     Irineu Martins de Lima Moura (imlm2) - initial API and implementation
 ******************************************************************************/
package br.ufpe.cin.imlm2.jbeat.soot.throwableset.generation;

import static br.ufpe.cin.imlm2.jbeat.soot.common.CommonExceptions.*;

import java.util.List;
import java.util.Set;

import soot.Body;
import soot.Immediate;
import soot.SootClass;
import soot.Value;
import soot.jimple.*;

import br.ufpe.cin.imlm2.jbeat.soot.throwableset.InstructionVisitor;

public class GeneratesVisitor extends InstructionVisitor {

	public GeneratesVisitor(Body b) {
		super(b);
	}

	@Override
	public void caseBreakpointStmt(BreakpointStmt stmt) {
		// Empty.
	}

	@Override
	public void caseInvokeStmt(InvokeStmt stmt) {
		this.cache.clear();
		this.checkNullCheckTag(stmt);
		this.checkArrayBoundsTag(stmt);
		this.filterTrappedOnes(stmt);
		this.exceptions.addAll(this.cache);
	}

	@Override
	public void caseAssignStmt(AssignStmt stmt) {
		this.cache.clear();
		this.investigateValue(stmt.getLeftOp());
		this.investigateValue(stmt.getRightOp());
		this.checkNullCheckTag(stmt);
		this.checkArrayBoundsTag(stmt);
		this.filterTrappedOnes(stmt);
		this.exceptions.addAll(this.cache);
	}

	@Override
	public void caseIdentityStmt(IdentityStmt stmt) {
		// Just name binding.
	}

	@Override
	public void caseEnterMonitorStmt(EnterMonitorStmt stmt) {
		this.cache.clear();
		this.investigateValue(stmt.getOp());
		this.checkNullCheckTag(stmt);
		this.filterTrappedOnes(stmt);
		this.exceptions.addAll(this.cache);
	}

	@Override
	public void caseExitMonitorStmt(ExitMonitorStmt stmt) {
		this.cache.clear();
		this.investigateValue(stmt.getOp());
		this.checkNullCheckTag(stmt);
		this.filterTrappedOnes(stmt);
		this.exceptions.addAll(this.cache);
	}

	@Override
	public void caseGotoStmt(GotoStmt stmt) {
		// Just asynchronous exceptions.
	}

	@Override
	public void caseIfStmt(IfStmt stmt) {
		this.cache.clear();
		this.investigateValue(stmt.getCondition());
		this.checkNullCheckTag(stmt);
		this.checkArrayBoundsTag(stmt);
		this.filterTrappedOnes(stmt);
		this.exceptions.addAll(this.cache);
	}

	@SuppressWarnings("unchecked")
	@Override
	public void caseLookupSwitchStmt(LookupSwitchStmt stmt) {
		this.cache.clear();
		this.investigateValue(stmt.getKey());
		List<Value> cases = stmt.getLookupValues();
		for (Value value : cases) {
			this.investigateValue(value);
		}
		this.checkNullCheckTag(stmt);
		this.checkArrayBoundsTag(stmt);
		this.filterTrappedOnes(stmt);
		this.exceptions.addAll(this.cache);
	}

	@Override
	public void caseNopStmt(NopStmt stmt) {
		// Empty.
	}

	@Override
	public void caseRetStmt(RetStmt stmt) {
		// Just asynchronous exceptions and IllegalMonitorStateException

	}

	@Override
	public void caseReturnStmt(ReturnStmt stmt) {
		// Just asynchronous exceptions and IllegalMonitorStateException
	}

	@Override
	public void caseReturnVoidStmt(ReturnVoidStmt stmt) {
		// Just asynchronous exceptions and IllegalMonitorStateException

	}

	@Override
	public void caseTableSwitchStmt(TableSwitchStmt stmt) {
		this.cache.clear();
		this.investigateValue(stmt.getKey());
		this.checkNullCheckTag(stmt);
		this.checkArrayBoundsTag(stmt);
		this.filterTrappedOnes(stmt);
		this.exceptions.addAll(this.cache);
	}

	@Override
	public void caseThrowStmt(ThrowStmt stmt) {
		// FIXME Can throw instructions raise exceptions during execution?
	}

	@Override
	public void defaultCase(Object obj) {
		throw new IllegalArgumentException("Stmt invalido");
	}

	protected void investigateValue(Value v) {
		if (v instanceof Ref) {
			investigate((Ref) v);
		} else if (v instanceof Expr) {
			investigate((Expr) v);
		} else if (v instanceof Immediate) {
			return;
		} else {
			throw new IllegalArgumentException("Value invalido");
		}
	}

	@SuppressWarnings("unchecked")
	private void investigate(Expr v) {
		if (v instanceof AddExpr) {
			investigateBinopExpr((BinopExpr) v);
		} else if (v instanceof AndExpr) {
			investigateBinopExpr((BinopExpr) v);
		} else if (v instanceof CastExpr) {
			this.cache.add(getCLASSCAST());
			investigateValue(((CastExpr) v).getOp());
		} else if (v instanceof CmpExpr) {
			investigateBinopExpr((BinopExpr) v);
		} else if (v instanceof CmpgExpr) {
			investigateBinopExpr((BinopExpr) v);
		} else if (v instanceof CmplExpr) {
			investigateBinopExpr((BinopExpr) v);
		} else if (v instanceof DivExpr) {
			Value divisor = ((DivExpr) v).getOp2();
			if (divisor instanceof IntConstant) {
				int div = ((IntConstant) divisor).value;
				if (div == 0)
					this.cache.add(getARITHMETIC());
			} else {
				this.cache.add(getARITHMETIC());
			}
			this.investigateBinopExpr((BinopExpr) v);
		} else if (v instanceof EqExpr) {
			this.investigateBinopExpr((BinopExpr) v);
		} else if (v instanceof GeExpr) {
			this.investigateBinopExpr((BinopExpr) v);
		} else if (v instanceof GtExpr) {
			this.investigateBinopExpr((BinopExpr) v);
		} else if (v instanceof InstanceOfExpr) {
			this.investigateValue(((InstanceOfExpr) v).getOp());
		} else if (v instanceof LeExpr) {
			this.investigateBinopExpr((BinopExpr) v);
		} else if (v instanceof LengthExpr) {
			this.investigateValue(((LengthExpr) v).getOp());
		} else if (v instanceof LtExpr) {
			this.investigateBinopExpr((BinopExpr) v);
		} else if (v instanceof MulExpr) {
			this.investigateBinopExpr((BinopExpr) v);
		} else if (v instanceof NeExpr) {
			this.investigateBinopExpr((BinopExpr) v);
		} else if (v instanceof NegExpr) {
			this.investigateValue(((NegExpr) v).getOp());
		} else if (v instanceof NewArrayExpr) {
			Value size = ((NewArrayExpr) v).getSize();
			if (size instanceof IntConstant) {
				int val = ((IntConstant) size).value;
				if (val < 0)
					this.cache.add(getNEGATIVEARRAYSIZE());
			} else
				this.cache.add(getNEGATIVEARRAYSIZE());
			this.investigateValue(size);
		} else if (v instanceof NewExpr) {
			return;
		} else if (v instanceof NewMultiArrayExpr) {
			List<Value> sizes = ((NewMultiArrayExpr) v).getSizes();
			boolean maybeNegative = false;
			for (Value size : sizes) {
				if (size instanceof IntConstant) {
					int val = ((IntConstant) size).value;
					if (val < 0)
						maybeNegative = true;
				} else
					maybeNegative = true;
				this.investigateValue(size);
			}
			if (maybeNegative)
				this.cache.add(getNEGATIVEARRAYSIZE());
		} else if (v instanceof OrExpr) {
			this.investigateBinopExpr((BinopExpr) v);
		} else if (v instanceof RemExpr) {
			// TODO RemExpr?
			this.investigateBinopExpr((BinopExpr) v);
		} else if (v instanceof ShlExpr) {
			this.investigateBinopExpr((BinopExpr) v);
		} else if (v instanceof ShrExpr) {
			this.investigateBinopExpr((BinopExpr) v);
		} else if (v instanceof SubExpr) {
			this.investigateBinopExpr((BinopExpr) v);
		} else if (v instanceof UshrExpr) {
			this.investigateBinopExpr((BinopExpr) v);
		} else if (v instanceof XorExpr) {
			this.investigateBinopExpr((BinopExpr) v);
		} else if (v instanceof InvokeExpr) {
			return;
		} else {
			throw new IllegalArgumentException("Expressao Invalida " + v);
		}

	}

	private void investigate(Ref v) {
		Set<SootClass> throwables = this.cache;
		if (v instanceof ArrayRef) {
			throwables.add(getNULLPOINTER());
			throwables.add(getARRAYINDEXOUTOFBOUNDS());
			this.investigateValue(((ArrayRef) v).getBase());
			this.investigateValue(((ArrayRef) v).getIndex());
		} else if (v instanceof CaughtExceptionRef) {
			return;
		} else if (v instanceof InstanceFieldRef) {
			throwables.add(getNULLPOINTER());
			this.investigateValue(((InstanceFieldRef) v).getBase());
		} else if (v instanceof ParameterRef) {
			return;
		} else if (v instanceof StaticFieldRef) {
			return;
		} else if (v instanceof ThisRef) {
			return;
		} else {
			throw new IllegalArgumentException("Invalid Ref - " + v);
		}

	}

	private void investigateBinopExpr(BinopExpr v) {
		investigateValue((v).getOp1());
		investigateValue((v).getOp2());
	}

}
